/*
 * 描述： 代码生成
 * 修改人： Weng.weng
 * 修改时间： 2021年6月30日
 * 项目： code-generator
 */
package com.tuozixuan.code.core;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.tuozixuan.code.config.CodeProperties;
import com.tuozixuan.code.model.CodeItem;
import com.tuozixuan.code.model.DbField;
import com.tuozixuan.code.model.JavaField;
import com.tuozixuan.code.model.Table;
import com.tuozixuan.code.ve.VEContext;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;

/**
 * 代码生成<br>
 * 
 * @author Weng.weng
 * @version [版本号, 2021年6月30日]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
public class CodeGenerator {
	
	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	
	private CodeProperties codeProperties;
	
	private TableInfoHelper tableInfoHelper;
	
	public CodeGenerator(CodeProperties codePropertie, TableInfoHelper tableInfoHelper) {
		this.codeProperties = codePropertie;
		this.tableInfoHelper = tableInfoHelper;
	}
	
	public void generate() {
		
		Table table = tableInfoHelper.getTableInfo(null, codeProperties.getTableName());
	 	this.generateCodeFile(table);
	}
	
	/**
	 * 
	 * 生成代碼文件<br>
	 *
	 * @param table 數據庫表信息
	 * @version [版本号, Jun 11, 2021]
	 * @author Weng.weng
	 */
    private void generateCodeFile(Table table) {
        String modelValName = this.getModelValName(table.getName());
        String modelName = StrUtil.upperFirst(modelValName);
        List<JavaField> javaFieldList = table.getFieldList().stream().map(field -> this.fieldToJavaField(field)).collect(Collectors.toList());
        
        Map<String, Object> templateParam = this.buildTemplateParam(table, javaFieldList, modelName, modelValName);
        List<CodeItem> codeItemList = codeProperties.getCodeItemList();
        codeItemList.forEach(codeItem -> this.generateCodeItemFile(templateParam, codeItem, modelName));
    }
	
	/**
	 * 
	 * 根據表名獲取模型變量名（首字母小写）<br>
	 *
	 * @param tableName 表名
	 * @return String 模型變量名（首字母小写）
	 * @version [版本号, Jun 11, 2021]
	 * @author Weng.weng
	 */
	private String getModelValName(String tableName) {
	    String removePrefixTableName = this.removePrefix(tableName, codeProperties.getRemovedPrefixList());
	    return StrUtil.toCamelCase(removePrefixTableName);
	}
	
	/**
	 * 
	 * 數據庫字段信息轉換為java字段信息<br>
	 *
	 * @param field 數據庫字段信息
	 * @return JavaField java字段信息
	 * @version [版本号, Jun 11, 2021]
	 * @author Weng.weng
	 */
	private JavaField fieldToJavaField(DbField field) {
        JavaField javaField = new JavaField();
        javaField.setName(StrUtil.toCamelCase(field.getName()));
        javaField.setFirstUpperName(StrUtil.upperFirst(javaField.getName()));
        
        String javaType = codeProperties.getDbTypeToJava().getOrDefault(field.getType(), "String");
        String jdbcType = codeProperties.getDbTypeToJdbcType().getOrDefault(field.getType(), "VARCHAR");
        
        if (StringUtils.isNotBlank(field.getComment())) {
            String simpleComment = field.getComment().replaceFirst("[\\[(（].*", "");
            javaField.setSimpleComment(simpleComment.trim());
        }
        
        javaField.setType(javaType);
        javaField.setComment(field.getComment());
        javaField.setDbField(field);
        javaField.setJdbcType(jdbcType);
        return javaField;
	}
	
	/**
	 * 
	 * 生成代碼文件<br>
	 *
	 * @param templateParam 模板文件動態參數
	 * @param codeItem 代碼子項信息
	 * @param modelName 模型名
	 * @version [版本号, Jun 11, 2021]
	 * @author Weng.weng
	 */
	private void generateCodeItemFile(Map<String, Object> templateParam, CodeItem codeItem, String modelName) {
	    
	    String basePath = codeProperties.getCodeTypeToBasePath().get(codeItem.getType());
	    String tempPath = StringUtils.removeEnd(basePath, "\\src\\main\\java");
	    tempPath = StringUtils.removeEnd(tempPath, "\\src\\main\\resources");
	    String moduleName = StringUtils.substringAfterLast(tempPath, "\\");
	    templateParam.put("moduleName", moduleName);
	    
	    templateParam.put("subPackage", codeItem.getSubPackage());
        String re = VEContext.INSTANCE.parseTemplate(codeProperties.getVmDir() + "/" + codeItem.getTemplateName(), templateParam);
        logger.info("{}:\n{}", codeItem.getTemplateName(), re);

        String codePath = this.getCodePath(codeItem, modelName);
        logger.info("codePath:{}", codePath);
        
        File file = FileUtil.touch(codePath);
        FileUtil.writeString(re, file, "UTF-8");
	}
	
	/**
	 * 
	 * 獲取代碼文件路徑名<br>
	 *
	 * @param codeItem 代碼項
	 * @param modelName 模型名
	 * @return String 代碼文件路徑名
	 * @version [版本号, Jun 11, 2021]
	 * @author Weng.weng
	 */
	private String getCodePath(CodeItem codeItem, String modelName) {
	    
	    String basePackage = codeProperties.getBasePackage();
	    if (StringUtils.startsWithIgnoreCase(codeItem.getTemplateName(), "Web")) {
	        basePackage = codeProperties.getWebBasePackage();
        }
		
	    String basePath = codeProperties.getCodeTypeToBasePath().get(codeItem.getType());
	    String fullPackage = null;
		if ("mybatis".equals(codeItem.getType()) || "html".equals(codeItem.getType())) {
		    fullPackage = codeItem.getSubPackage();
		} else {
		    fullPackage = String.join(".", basePackage, codeItem.getSubPackage());
		}
		String fullPackagePath = fullPackage.replace('.', '\\');
		String fileName = modelName + codeItem.getSuffix();
		return String.format("%s\\%s\\%s", basePath, fullPackagePath, fileName);
	}
	
	/**
	 * 
	 * 构建模板参数<br>
	 *
	 * @param table 表信息
	 * @param javaFieldList java字段信息
	 * @param modelName 模型名
	 * @param modelValName 模型名（首字母小写）
	 * @return Map<String,Object> [返回类型说明]
	 * @version [版本号, Jun 9, 2021]
	 * @author Weng.weng
	 */
    private Map<String, Object> buildTemplateParam(Table table, List<JavaField> javaFieldList,
            String modelName, String modelValName) {
        Map<String, Object> param = new HashMap<String, Object>();
        param.put("modelName", modelName);
        param.put("modelValName", modelValName);
        param.put("modelComment", this.removeSuffix(table.getComment(), Arrays.asList("表", "table")));
        param.put("currentDate", new SimpleDateFormat(codeProperties.getDateFormatInComment()).format(new Date()));
        param.put("basePackage", codeProperties.getBasePackage());
        param.put("webBasePackage", codeProperties.getWebBasePackage());
        param.put("packageName", codeProperties.getPackageName());
        param.put("fieldList", javaFieldList);
        param.put("table", table);
        param.put("author", codeProperties.getAuthorInComment());
        return param;
    }
	
	/**
     * 
     * 如果文本存在指定的前缀则移除<br>
     *
     * @param text 文本
     * @param removedPrefixList 需移除的前缀文本列表
     * @return String 处理后的文本
     * @version [版本号, Jun 9, 2021]
     * @author Weng.weng
     */
	private String removePrefix(String text, List<String> removedPrefixList) {
	    return this.removeText(text, removedPrefixList, (String supplyText) -> StrUtil.removePrefixIgnoreCase(text, supplyText));
	}
	
	/**
	 * 
	 * 如果文本存在指定的后缀则移除<br>
	 *
	 * @param text 文本
	 * @param removedSuffixList 需移除的后缀文本列表
	 * @return String 处理后的文本
	 * @version [版本号, Jun 9, 2021]
	 * @author Weng.weng
	 */
	private String removeSuffix(String text, List<String> removedSuffixList) {
	    return this.removeText(text, removedSuffixList, (String supplyText) -> StrUtil.removeSuffixIgnoreCase(text, supplyText));
	}

	/**
	 * 
	 * 字符串文本信息处理<br>
	 *
	 * @param text 待处理的文本
	 * @param supplyTextList 文本列表
	 * @param function 处理函数
	 * @return String 处理后的文本
	 * @version [版本号, Jun 9, 2021]
	 * @author Weng.weng
	 */
	private String removeText(String text, List<String> supplyTextList, Function<String,String> function) {
	    
	    for (String supplyText : supplyTextList) {
	        String localText = function.apply(supplyText);
            if (!StrUtil.equals(localText, text)) {
                return localText;
            }
        }
        return text;
	}
}
